module render.sampler;

import render.abstractrenderer;
import render.abstractsampler;
import canvas.abstractcanvas;
import canvas.floatcanvas;
import utils.vector2;
import utils.color;
import std.math;
import std.stdio;
import std.c.math;
import utils.mt19937ar;

private void randPerm(int n, int perm[]) {
    for (int i = 0; i < n; i++) {
        perm[i] = i;
    }
    for (int i = 1; i < n; i++) {
        auto ti = genrand_int32() % (i + 1);
        auto tmp = perm[i];
        perm[i] = perm[ti];
        perm[ti] = tmp;
    }
}

private int spectralTest(int a, int c, int m) {
    // S1
    int t = 2;
    int h = a;
    int hs = m;
    int p = 1;
    int ps = 0;
    int r = a;
    int s = 1 + a*a;
    int u;
    int v;
    // S2
    do {
        int q = hs/h;
        u = hs - q*h;
        v = ps - q*p;
        int tmp = u*u + v*v;
        if ( tmp < s) {
            s = tmp;
            hs = h;
            h = u;
            ps = p;
            p = v;
        } else {
            break;
        }
    } while (true);
    // S3
    u = u - h;
    v = v - p;
    int tmp = u*u + v*v;
    if ( tmp < s) {
        s = tmp;
    }
    return cast(int)std.math.sqrt(cast(float)s);
}

public enum AntialiasMethod {
    NONE = 0,
    MERSENNE = 1,
    GRID = 2,
    RANDGRID = 3,
    LATIN = 4,
    R1 = 5,
};

alias void delegate(inout Vector2f pos, out RGBColor col) SampleFunction;

public class AntialiasSampler : AbstractSampler {
    private struct SamplerInfo {
        AntialiasMethod id;
        SampleFunction f;
        char[] name;
    }

    private SamplerInfo[AntialiasMethod] mSamplers;

    private AntialiasMethod mAntialiasMethod;
    private int mLinCount;
    private int mSquareCount;
    private SampleFunction mCurFunc;

    public this() {
        addFunction(AntialiasMethod.NONE, &sampleNone, "Pixel Center");
        addFunction(AntialiasMethod.MERSENNE, &sampleMersenne, "Mersenne");
        addFunction(AntialiasMethod.GRID, &sampleGrid, "Grid");
        addFunction(AntialiasMethod.RANDGRID, &sampleRandgrid, "Randomized grid");
        addFunction(AntialiasMethod.LATIN, &sampleLatin, "Latin hypercube");
        addFunction(AntialiasMethod.R1, &sampleR1, "Rang 1 Grid");

        antialiasMethod = AntialiasMethod.NONE;
        linearSampleCount = 16;
        squareSampleCount = 4;
    }

    private void addFunction(AntialiasMethod m, SampleFunction f, char[] name) {
        SamplerInfo r;
        r.id = m;
        r.f = f;
        r.name = name;
        mSamplers[r.id] = r;
    }

    public void initSampler()
    {
        //
    }

    public void antialiasMethod(AntialiasMethod id) {
        mAntialiasMethod = id;
        mCurFunc = mSamplers[id].f;

        writefln("[S] Sampling method: %s", mSamplers[id].name);
    }
    public AntialiasMethod antialiasMethod() {
        return mAntialiasMethod;
    }

    public void listModes() {
        writefln("Antialiasing modes:");
        foreach (SamplerInfo sinf; mSamplers) {
            writefln("  %d: %s",cast(int)sinf.id,sinf.name);
        }
    }

    public void linearSampleCount(int sCount) {
        mLinCount = sCount;

        writefln("[S] Sample count (linear): %s", mLinCount);
    }
    public int linearSampleCount() {
        return mLinCount;
    }

    public void squareSampleCount(int sCount) {
        mSquareCount = sCount;
        writefln("[S] Sample count (square): %s", mSquareCount);
    }
    public int squareSampleCount() {
        return mSquareCount;
    }

    public void sample(inout Vector2f pos, out RGBColor col) {
        mCurFunc(pos,col);
    }

    private void sampleNone(inout Vector2f pos, out RGBColor col) {
        pos.x += 0.5f;
        pos.y += 0.5f;
        col = mRenderFunc(pos);
    }

    //sample n random points inside a pixel using mersenne twister
    private void sampleMersenne(inout Vector2f pos, out RGBColor col) {
        int n = mLinCount;
        col.r = col.b = col.g = 0.0f;
        for (int i = 0; i < n; i++) {
            Vector2f r = Vector2f(genrand_real2(),genrand_real2());
            col += mRenderFunc(pos+r);
        }
        col /= n;
    }

    //sample center of m x m subpixels
    private void sampleGrid(inout Vector2f pos, out RGBColor col) {
        int m = mSquareCount;
        col.r = col.b = col.g = 0.0f;
        for (int dx = 0; dx < m; dx++) {
            for (int dy = 0; dy < m; dy++) {
                float v = 1.0f/(m*2);
                Vector2f d = Vector2f(cast(float)dx/m + v, cast(float)dy/m + v);
                col += mRenderFunc(pos+d);
            }
        }
        col /= (m*m);
    }

    //sample random point inside m x m subpixels
    private void sampleRandgrid(inout Vector2f pos, out RGBColor col) {
        int m = mSquareCount;
        for (int dx = 0; dx < m; dx++) {
            for (int dy = 0; dy < m; dy++) {
                Vector2f r = Vector2f(genrand_real2()/m + cast(float)dx/m,
                    genrand_real2()/m + cast(float)dy/m);
                col += mRenderFunc(pos+r);
            }
        }
        col /= (m*m);
    }

    //use a random permutation to sample one subpixel each row+col
    private void sampleLatin(inout Vector2f pos, out RGBColor col) {
        int n = mLinCount;
        int perm[] = new int[n];
        randPerm(n,perm);
        for (int i = 0; i < n; i++) {
            Vector2f r = Vector2f(genrand_real2()/n + cast(float)i/n,
                genrand_real2()/n + cast(float)perm[i]/n);
            col += mRenderFunc(pos+r);
        }
        col /= n;
    }

    private void sampleR1(inout Vector2f pos, out RGBColor col) {
        int n = mLinCount;

        //xxx hier Spektraltest einfgen
        static float a = PI*1000;

        //Cranley-Patterson-Rotation
        float cpShiftX = genrand_real2();
        float cpShiftY = genrand_real2();

        for (int i = 0; i < n; i++) {
            Vector2f p = Vector2f(fmodf(cast(float)i/n + cpShiftX, 1.0f),
                fmodf((a*i)/n + cpShiftY, 1.0f));
            col += mRenderFunc(pos+p);
        }
        col /= n;
    }
}

